package com.kevin.util;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * kevin<br/>
 * 2020/9/2 20:22<br/>
 * 正则表达式工具类，能够用于处理正则匹配时由于回溯次数过多所导致的服务卡死问题
 */
public class RegexUtil {

    // 演示正则表达式对给定输入发生灾难性回溯时的行为
    public static void main(String[] args) {
        Matcher matcher = createMatcherWithTimeout(
                "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "(x+x+)+y", 2000);
        System.out.println(matcher.matches());
    }

    /**
     * 创建带超时时间的Matcher
     * @param stringToMatch     要匹配的字符串
     * @param regularExpression 正则表达式
     * @param timeoutMillis     超时时间
     * @return 带超时时间的Matcher
     */
    public static Matcher createMatcherWithTimeout(String stringToMatch, String regularExpression, int timeoutMillis) {
        Pattern pattern = Pattern.compile(regularExpression);
        return createMatcherWithTimeout(stringToMatch, pattern, timeoutMillis);
    }

    /**
     * 创建带超时时间的Matcher
     * @param stringToMatch             要匹配的字符串
     * @param regularExpressionPattern  正则表达式
     * @param timeoutMillis             超时时间
     * @return 带超时时间的Matcher
     */
    public static Matcher createMatcherWithTimeout(String stringToMatch, Pattern regularExpressionPattern, int timeoutMillis) {
        if (timeoutMillis < 0) {
            return regularExpressionPattern.matcher(stringToMatch);
        }
        CharSequence charSequence = new TimeoutRegexCharSequence(stringToMatch, regularExpressionPattern.pattern(), timeoutMillis);
        return regularExpressionPattern.matcher(charSequence);
    }

    /**
     * 带超时时间的CharSequence
     */
    private static class TimeoutRegexCharSequence implements CharSequence {

        private final String inner;             // 字符串
        private final String regularExpression; // 正则表达式
        private final int timeoutMillis;        // 超时时间
        private final long timeoutTime;         // 具体的超时时刻

        public TimeoutRegexCharSequence(String input, String regularExpression, int timeoutMillis) {
            this.inner = input;
            this.regularExpression = regularExpression;
            this.timeoutMillis = timeoutMillis;
            this.timeoutTime = System.currentTimeMillis() + timeoutMillis;
        }

        @Override
        public char charAt(int index) {
            if (System.currentTimeMillis() > timeoutTime) {
                throw new RuntimeException("处理正则表达式超时, 超时时间: " + timeoutMillis + ", 正则表达式: " + regularExpression + ", 字符串: " + inner);
            }
            return inner.charAt(index);
        }

        @Override
        public int length() {
            return inner.length();
        }

        @Override
        public CharSequence subSequence(int start, int end) {
            return new TimeoutRegexCharSequence(inner.substring(start, end), regularExpression, timeoutMillis);
        }

        @Override
        public String toString() {
            return inner.toString();
        }
    }
}